﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Arduino.Firmata
{
    public class Const
    {
        public const int INPUT_SIZE = 16;
        public const int TOTAL_PINS = 24;
        public const int MAX_DATA_BYTES = 32;
    }

    public class PinConfig
    {
        public PinConfig(Types.Pins pin)
        {
            this.Pin = pin;
        }

        public Types.Pins Pin
        {
            get;
            private set;
        }

        public Types.PinMode Mode
        {
            get;
            private set;
        }
    }

    /// <summary>
    /// Класс для обмена данными с микроконтроллером Arduino по протоколу Firmata (http://firmata.org).
    /// </summary>
    public class Firmata
    {
        #region Variables
        private ConnectionTypes.ConnectionBase mConnection = null;

        private Dictionary<Types.Pins, Types.PinMode> pinsConfig = new Dictionary<Types.Pins, Types.PinMode>();
        public Dictionary<Types.Pins, int> pinsValues = new Dictionary<Types.Pins, int>();
        public Dictionary<Types.Pins, bool> pinsReportMode = new Dictionary<Types.Pins, bool>();

        // Firmata version
        int majorVersion;
        int minorVersion;

        int[] digitalOutputData = new int[Const.INPUT_SIZE];
        int[] digitalInputData = new int[Const.INPUT_SIZE];

        int parse_count;
        int parse_command_len;
        int[] parse_buf = new int[4096];

        public List<string> StringsBuffer = new List<string>();

        #endregion

        /// <summary>
        /// Конструктор принимает указатель на источник данных, унаследованный от класса ConnectionTypes.ConnectionBase. 
        /// </summary>
        /// <param name="connection"></param>
        public Firmata(ConnectionTypes.ConnectionBase connection)
        {
            if (connection == null) throw new Exception(Properties.Resources.Error_Firmata_InitializeEmptyConnection);
            connection.OnData += new ConnectionTypes.EventConnectionData(_update);
            //connection.OnError += new ConnectionTypes.EventConnectionError(connection_OnError);
            mConnection = connection;

            foreach (var val in Enum.GetValues(typeof(Types.PinMode)))
                pinsValues.Add((Types.Pins)val, 0);

            for (int i = 0; i < Const.INPUT_SIZE; i++)
            {
                digitalOutputData[i] = 0;
                digitalInputData[i] = 0;
            }

            majorVersion = 0;
            minorVersion = 0;

            parse_count = 0;
            parse_command_len = 0;
            Array.Clear(parse_buf, 0, parse_buf.Length);
        }

        ~Firmata()
        {
            //this.close();
        }

        //
        // Arduino functions 
        //

        /// <summary>
        /// Возвращает последнее известное значение цифрового порта: HIGH/LOW для режима OUTPUT/INPUT или значение из диапазона 0-255 для PWM-режима.
        /// </summary>
        /// <param name="pin">Цифровой порт (D1-DXX), значение которого может быть возвращено. D1 и D2 используются для подключения COM-порта.</param>
        /// <returns>Возвращает последнее значение, полученное для этого порта. Возвращает -1, если порт указан некорректно.</returns>
        public int digitalRead(Types.Pins pin)
        {
            int _pin = (int)pin;
            if (_pin >= 0 && _pin < Const.INPUT_SIZE) return (digitalInputData[_pin >> 3] >> (_pin & 0x07)) & 0x01;
            return -1;
        }

        /// <summary>
        /// Возвращает последнее известное значение аналогового порта: от 0 (0 вольт) до 1023 (5 вольт).
        /// </summary>
        /// <param name="_pin">Аналоговый порт (A0-AXX), значение которого может быть возвращено.</param>
        /// <returns></returns>
        public int analogRead(Types.Pins pin)
        {
            if (pinsValues.ContainsKey(pin)) return pinsValues[pin];
            return -1;
        }

        /// <summary>
        /// Устанавливает режим работы порта.
        /// </summary>
        /// <param name="_pin">Порт, для которого следует установить режим работы.</param>
        /// <param name="mode">Устанавливаемый режим работы порта.</param>
        public void setPinMode(Types.Pins pin, Types.PinMode mode)
        {
            int _pin = (int)pin;
            if (_pin >= 0 && _pin < Const.INPUT_SIZE)
            {
                if (this.pinsConfig.ContainsKey(pin)) this.pinsConfig[pin] = mode;
                else this.pinsConfig.Add(pin, mode);

                mConnection.Write((int)Types.Messages.SET_PIN_MODE);
                mConnection.Write(_pin);
                mConnection.Write((int)mode);
            }
        }

        /// <summary>
        /// Возвращает режим работы порта, если режим был ранее установлен с помощью функции setPinMode. В противном случае вернет Types.PinMode.None.
        /// </summary>
        /// <param name="_pin">Порт, для которого следует получить режим работы.</param>
        /// <returns>Режим работы порта или Types.PinMode.None, если режим не был установлен с помощью setPinMode ранее.</returns>
        public Types.PinMode getPinMode(Types.Pins pin)
        {
            if (pinsConfig.ContainsKey(pin)) return this.pinsConfig[pin];
            return Types.PinMode.None;
        }

        /// <summary>
        /// Возвращает список портов, для которых установлен указанный в параметрах режим работы.
        /// </summary>
        /// <param name="mode">Режим работы портов.</param>
        /// <returns>Список идентификаторов портов.</returns>
        public List<Types.Pins> getPinList(Types.PinMode mode)
        {
            List<Types.Pins> list = new List<Types.Pins>();
            try
            {
                foreach (var pin in pinsConfig)
                    if (pin.Value == mode) list.Add(pin.Key);
            }
            catch (Exception) { }

            return list;
        }

        /// <summary>
        /// Возвращает список портов.
        /// </summary>
        /// <returns>Список идентификаторов портов.</returns>
        public List<Types.Pins> getPinList()
        {
            List<Types.Pins> list = new List<Types.Pins>();
            try
            {
                foreach (var pin in pinsConfig) list.Add(pin.Key);
            }
            catch (Exception) { }

            return list;
        }

        /// <summary>
        /// Записывает значение в указанный цифровой порт в цифровом режиме (порт должен быть переключен в режим INPUT/OUTPUT вызовом setPinMode).
        /// </summary>
        /// <param name="pin">Порт, для которого записывается значение.</param>
        /// <param name="value">Записываемое значение порта. Может быть High (5 вольт) или Low (0 вольт)</param>
        public void digitalWrite(Types.Pins pin, Types.PinDigitalValue value)
        {
            int _pin = (int)pin;
            if (_pin > Const.INPUT_SIZE)
            {
                Console.WriteLine("[!][ArduinoFirmata] Error: too big _pin number: {0}", _pin);
                return;
            }

            int portNumber = (_pin >> 3) & 0x0F;

            if (value == Types.PinDigitalValue.Low) digitalOutputData[portNumber] &= ~(1 << (_pin & 0x07));
            else if (value == Types.PinDigitalValue.High) digitalOutputData[portNumber] |= (1 << (_pin & 0x07));
            pinsValues[pin] = (int)value;

            mConnection.Write((int)Types.Messages.DIGITAL_MESSAGE | portNumber);
            mConnection.Write(digitalOutputData[portNumber] & 0x7F);
            mConnection.Write(digitalOutputData[portNumber] >> 7);
        }

        /// <summary>
        /// Записывает аналоговое значение в указанный цифровой порт (порт должен быть переключен в режим PWM вызовом setPinMode).
        /// </summary>
        /// <param name="pin">Порт, для которого записывается значение. PWM-режим поддерживается только для 9, 10 и 11 портов.</param>
        /// <param name="value">Записываемое значение порта. Диапазон значений от 0 (0 вольт) до 255 (5 вольт).</param>
        public void analogWrite(Types.Pins pin, int value)
        {
            int _pin = (int)pin;
            pinsValues[pin] = value;
            mConnection.Write((int)Types.Messages.ANALOG_MESSAGE | (_pin & 0x0F));
            mConnection.Write(value & 0x7F);
            mConnection.Write(value >> 7);
        }

        /// <summary>
        /// Устанавливает интервал, в течение которого Firmata-прошивка микроконтроллера Arduino перечитывает и отправляет данные портов.
        /// </summary>
        /// <param name="ms">Интервал времени в микросекундах.</param>
        public void sendSamplingInterval(int ms)
        {
            char[] ch = new char[2] { '1', '0' };
            this.sendSysEx(0x7A, ch, 2);
        }

        /// <summary>
        /// Отправка системного сообщения с указанными данными микроконтроллеру Arduino.
        /// </summary>
        /// <param name="command">Идентификатор отправляемой команды.</param>
        /// <param name="data">Указатель на буфер с отправляемыми данными команды.</param>
        /// <param name="length">Длина буфера с данными.</param>
        public void sendSysEx(Types.Messages command, char[] data, int length)
        {
            sendSysEx((int)command, data, length);
        }

        /// <summary>
        /// Отправка системного сообщения с указанными данными микроконтроллеру Arduino.
        /// </summary>
        /// <param name="command">Идентификатор отправляемой команды.</param>
        /// <param name="data">Указатель на буфер с отправляемыми данными команды.</param>
        /// <param name="length">Длина буфера с данными.</param>
        public void sendSysEx(Types.Other command, char[] data, int length)
        {
            sendSysEx((int)command, data, length);
        }

        /// <summary>
        /// Отправка системного сообщения с указанными данными микроконтроллеру Arduino.
        /// </summary>
        /// <param name="command">Идентификатор отправляемой команды.</param>
        /// <param name="data">Указатель на буфер с отправляемыми данными команды.</param>
        /// <param name="length">Длина буфера с данными.</param>
        public void sendSysEx(int command, char[] data, int length)
        {
            if (data != null && length > 0)
            {
                mConnection.Write((int)Types.Messages.START_SYSEX);
                mConnection.Write(command);
                for (int i = 0; i < length; i++)
                {
                    mConnection.Write(data[i]);
                }
                mConnection.Write((int)Types.Messages.END_SYSEX);
            }
        }

        /// <summary>
        /// Отправка заголовка, обозначающего начало передачи системного сообщения микроконтроллеру Arduino.
        /// </summary>
        public void sendSysExBegin()
        {
            mConnection.Write((int)Types.Messages.START_SYSEX);
        }

        /// <summary>
        /// Отправка заголовка, обозначающего конец передачи системного сообщения микроконтроллеру Arduino.
        /// </summary>
        public void sendSysExEnd()
        {
            mConnection.Write((int)Types.Messages.END_SYSEX);
        }

        /// <summary>
        /// Отправка строкового сообщения микроконтроллеру Arduino.
        /// </summary>
        /// <param name="str">Буфер с отправляемой строкой.</param>
        /// <param name="length">Длина отправляемой строки.</param>
        public void sendString(char[] str, int length)
        {
            if (str != null && length > 0)
            {
                mConnection.Write((int)Types.Messages.START_SYSEX);
                mConnection.Write((int)Types.Messages.FIRMATA_STRING);
                for (int i = 0; i < length; i++)
                {
                    int value = (int)str[i];
                    mConnection.Write(value & 0x7F);
                    mConnection.Write(value >> 7);
                }
                mConnection.Write((int)Types.Messages.END_SYSEX);
            }
        }

        /// <summary>
        /// Отправляет запрос на получение версии протокола Firmata, используемого в прошивке микроконтроллера Arduino.
        /// </summary>
        public void sendVersionRequest()
        {
            mConnection.Write((int)Types.Messages.REPORT_VERSION);
        }

        /// <summary>
        /// Отправляет запрос на получение версии прошивки микроконтроллера Arduino.
        /// </summary>
        public void sendFirmwareRequest()
        {
            mConnection.Write((int)Types.Messages.REPORT_FIRMWARE);
        }

        /// <summary>
        /// Отправляет команду перезагрузки микроконтроллера Arduino.
        /// </summary>
        public void sendReset()
        {
            mConnection.Write((int)Types.Messages.SYSTEM_RESET);
        }

        /// <summary>
        /// Запрашивает текущее состояние порта у микроконтроллера Arduino.
        /// </summary>
        /// <param name="pin">Номер опрашиваемого порта.</param>
        public void sendRequestPinState(Types.Pins pin)
        {
            char[] s = new char[1];
            s[0] = (char)(int)pin;
            this.sendSysEx(Types.Messages.PIN_STATE_QUERY, s, 1);
        }

        /// <summary>
        /// Задает режим оповещения порта. Если state равен true, то микроконтроллер Arduino присылает данные о значениях на порту через промежуток времени, заданный командой sendSamplingInterval.
        /// </summary>
        /// <param name="pin">Номер порта.</param>
        /// <param name="state">Режим оповещения. Если равен true, то микроконтроллер Arduino присылает данные о значениях на порту через промежуток времени, заданный командой sendSamplingInterval.</param>
        public void sendSetPinReporting(Types.Pins pin, bool state)
        {
            int _pin = (int)pin;

            if (!pinsReportMode.ContainsKey(pin)) pinsReportMode.Add(pin, state);
            else pinsReportMode[pin] = state;

            int state_val = (state ? 1 : 0);
            if (_pin >= 0 && _pin < 16)
            {
                mConnection.Write((int)Types.Messages.REPORT_DIGITAL | (_pin & 0x0F));
                mConnection.Write(state_val & 0x7F);
                mConnection.Write(state_val >> 7);

                //mConnection.Write((int)Types.Messages.REPORT_DIGITAL);
                //mConnection.Write((int)pin);
                //mConnection.Write((state ? 1 : 0));
            }
            else if (_pin >= 16 && _pin <= 23)
            {
                mConnection.Write((int)Types.Messages.REPORT_ANALOG | ((_pin-16) & 0x0F));
                mConnection.Write(state_val & 0x7F);
                mConnection.Write(state_val >> 7);

                //mConnection.Write((int)Types.Messages.REPORT_ANALOG);
                //mConnection.Write((int)pin);
                //mConnection.Write((state ? 1 : 0));
            }
        }

        /// <summary>
        /// Возвращает режим оповещения указанного порта. Значение берется из внутреннего кеша, обновляющегося после каждого получения SYSEX-сообщения PIN_REPORT_RESPONSE. 
        /// </summary>
        /// <param name="pin">Номер порта.</param>
        /// <returns>Режим оповещения.</returns>
        public bool getPinReportMode(Types.Pins pin)
        {
            if (pinsReportMode.ContainsKey(pin)) return pinsReportMode[pin];
            else return false;
        }

        /// <summary>
        /// Возвращает режимы оповещения портов. Значения берутся из внутреннего кеша, обновляющегося после каждого получения SYSEX-сообщения PIN_REPORT_RESPONSE. 
        /// </summary>
        /// <returns>Список режимов оповещения для всех портов.</returns>
        public Dictionary<Types.Pins, bool> getPinsReportMode()
        {
            Dictionary<Types.Pins, bool> list = new Dictionary<Types.Pins, bool>();
            try
            {
                foreach (var rep in pinsConfig)
                {
                    int _pin = (int)rep.Key;
                    if (_pin >= 0 && _pin < Const.TOTAL_PINS)
                    {
                        if (pinsReportMode.ContainsKey(rep.Key)) list.Add(rep.Key, true);
                        else list.Add(rep.Key, true);
                    }
                }
            }
            catch (Exception) { }
            return list;
        }

        /// <summary>
        /// Запрашивает режим оповещения указанного порта. Микроконтроллер Arduino присылает текущее состояние оповещения.
        /// </summary>
        /// <param name="pin">Номер порта.</param>
        public void sendPinReportQuery(Types.Pins pin)
        {
            char[] s = new char[1];
            s[0] = (char)(int)pin;
            this.sendSysEx(Types.Messages.PIN_REPORT_QUERY, s, 1);
        }

        /// <summary>
        /// Устанавливает версию протокола Firmata, используемого в прошивке микроконтроллера Arduino.
        /// </summary>
        /// <param name="majorVersion"></param>
        /// <param name="minorVersion"></param>
        public void setVersion(int majorVersion, int minorVersion)
        {
            Console.WriteLine("[i][ArduinoFirmata] set version: {0}.{1}", majorVersion, minorVersion);
            this.majorVersion = majorVersion;
            this.minorVersion = minorVersion;
        }

        #region Parse incoming data
        public void _update(byte[] data, int length)
        {
            int[] buf2 = new int[length];
            Array.Clear(buf2, 0, buf2.Length);

            for (int i = 0; i < length; i++) buf2[i] = data[i];

            this.parse(buf2, length);
        }

        private void parse(int[] buf, int len)
        {
            //int[] p, end;

            //p = buf;
            //end = p + len;
            for (int i = 0; i < len; i++)
            {
                int msn = buf[i] & 0xF0;
                if (msn == (int)Types.Messages.ANALOG_MESSAGE || msn == (int)Types.Messages.DIGITAL_MESSAGE || buf[i] == (int)Types.Messages.REPORT_VERSION)
                {
                    parse_command_len = 3;
                    parse_count = 0;
                }
                else if (msn == (int)Types.Messages.REPORT_ANALOG || msn == (int)Types.Messages.REPORT_DIGITAL)
                {
                    parse_command_len = 2;
                    parse_count = 0;
                }
                else if (buf[i] == (int)Types.Messages.START_SYSEX)
                {
                    parse_count = 0;
                    parse_command_len = parse_buf.Length;
                }
                else if (buf[i] == (int)Types.Messages.END_SYSEX)
                {
                    parse_command_len = parse_count + 1;
                }
                else if ((buf[i] & 0x80) == 0x80)
                {
                    parse_command_len = 1;
                    parse_count = 0;
                }
                if (parse_count < parse_buf.Length)
                {
                    parse_buf[parse_count++] = buf[i];
                }
                if (parse_count == parse_command_len)
                {
                    this.doMessage();
                    parse_count = parse_command_len = 0;
                }
            }
        }

        private void doMessage()
        {
            int cmd = (parse_buf[0]);// & 0xF0);

            //	printf("[i][ArduinoFirmata] message, %d bytes, %02X\n", parse_count, parse_buf[0]);
            if (((parse_buf[0] & 0xF0) == (int)Types.Messages.ANALOG_MESSAGE || cmd == (int)Types.Messages.ANALOG_MESSAGE) && parse_count == 3)
            {
                int analog_ch = (parse_buf[0] & 0x0F);
                int analog_val = parse_buf[1] | (parse_buf[2] << 7);
                //Console.WriteLine("[i][ArduinoFirmata] A{0} = {1}", analog_ch, analog_val);
                pinsValues[Types.Types.AnalogPinToTotal(analog_ch)] = analog_val;
                return;
            }
            if (cmd == (int)Types.Messages.DIGITAL_MESSAGE && parse_count == 3)
            {
                int port_num = (parse_buf[0] & 0x0F);
                int port_val = parse_buf[1] | (parse_buf[2] << 7);
                int pin = port_num * 8;
                //Console.WriteLine("[i][ArduinoFirmata] port_num = {0}, port_val = {1}, _pin = {2}", port_num, port_val, _pin);
                digitalInputData[port_num] = port_val;
                pinsValues[(Types.Pins)pin] = port_val;
                return;
            }
            if (cmd == (int)Types.Messages.REPORT_VERSION && parse_count == 3)
            {
                this.setVersion(parse_buf[1], parse_buf[2]);
            }

            if (parse_buf[0] == (int)Types.Messages.START_SYSEX && parse_buf[parse_count - 1] == (int)Types.Messages.END_SYSEX)
            {
                // Sysex message
                if (parse_buf[1] == (int)Types.Messages.REPORT_FIRMWARE)
                {
                    char[] name = new char[140];
                    int len = 0;
                    for (int i = 4; i < parse_count - 2; i += 2)
                    {
                        int t = (parse_buf[i] & 0x7F)
                          | ((parse_buf[i + 1] & 0x7F) << 7);

                        name[len++] = (char)t;
                    }
                    name[len++] = '-';
                    name[len++] = (char)(parse_buf[2] + (int)'0');
                    name[len++] = '.';
                    name[len++] = (char)(parse_buf[3] + (int)'0');
                    name[len++] = (char)0;
                    Console.WriteLine("[i][ArduinoFirmata] firmata name: {0}", name);
                    // query the board's capabilities only after hearing the
                    // REPORT_FIRMWARE message.  For boards that reset when
                    // the port open (eg, Arduino with reset=DTR), they are
                    // not ready to communicate for some time, so the only
                    // way to reliably query their capabilities is to wait
                    // until the REPORT_FIRMWARE message is heard.
                    int[] buf = new int[80];
                    len = 0;
                    buf[len++] = (int)Types.Messages.START_SYSEX;
                    buf[len++] = (int)Types.Messages.ANALOG_MAPPING_QUERY; // read analog to _pin # info
                    buf[len++] = (int)Types.Messages.END_SYSEX;
                    buf[len++] = (int)Types.Messages.START_SYSEX;
                    buf[len++] = (int)Types.Messages.CAPABILITY_QUERY; // read capabilities
                    buf[len++] = (int)Types.Messages.END_SYSEX;
                    for (int i = 0; i < 16; i++)
                    {
                        buf[len++] = 0xC0 | i;  // report analog
                        buf[len++] = 1;
                        buf[len++] = 0xD0 | i;  // report digital
                        buf[len++] = 1;
                    }
                    mConnection.Write(buf, len);
                }
                else if (parse_buf[1] == (int)Types.Messages.CAPABILITY_RESPONSE)
                {
                    Console.WriteLine("[i][ArduinoFirmata] CAPABILITY_RESPONSE");
                }
                else if (parse_buf[1] == (int)Types.Messages.ANALOG_MAPPING_RESPONSE)
                {
                    Console.WriteLine("[i][ArduinoFirmata] ANALOG_MAPPING_RESPONSE");
                    return;
                }
                else if (parse_buf[1] == (int)Types.Messages.PIN_STATE_RESPONSE && parse_count >= 6)
                {
                    try
                    {
                        Arduino.Firmata.Types.Pins pin = (Arduino.Firmata.Types.Pins)parse_buf[2];
                        Arduino.Firmata.Types.PinMode mode = (Arduino.Firmata.Types.PinMode)parse_buf[3];
                        int _value = parse_buf[4];
                        if (parse_count > 6) _value |= (parse_buf[5] << 7);
                        if (parse_count > 7) _value |= (parse_buf[6] << 14);

                        pinsConfig[pin] = mode;
                        pinsValues[pin] = _value;
                        if (mode == Types.PinMode.Input || mode == Types.PinMode.Output)
                        {
                            Arduino.Firmata.Types.PinDigitalValue value = (Types.PinDigitalValue)_value;
                            //Console.WriteLine("[i][ArduinoFirmata] PIN_STATE_RESPONSE: pin = {0}, pin state = {1}, pin value = {2}", pin, mode, value);
                        }
                        else if (mode == Types.PinMode.PWM)
                        {
                            int value = _value;
                            //Console.WriteLine("[i][ArduinoFirmata] PIN_STATE_RESPONSE: pin = {0}, pin state = {1}, pin value = {2}", pin, mode, value);
                        }
                        else
                        {
                            //Console.WriteLine("[i][ArduinoFirmata] PIN_STATE_RESPONSE: pin = {0}, pin state = {1}, pin value = {2}", pin, mode, _value);
                        }

                    }
                    catch (Exception)
                    {

                    }
                }
                else if (parse_buf[1] == (int)Types.Messages.STRING_DATA)
                {
                    //int len = parse_buf[2] | (parse_buf[3] << 7);
                    int len = (parse_count - 3) / 2;
                    char[] s = new char[len];
                    try
                    {
                        for (int i = 0; i < len; i++)
                        {
                            int char_val = parse_buf[2 + i*2] | (parse_buf[2 + i*2 + 1] << 7);
                            s[i] = (char)char_val;
                        }
                    }
                    catch (Exception)
                    {

                    }

                    string str = new string(s);
                    StringsBuffer.Add(str);
                    Console.WriteLine("[i][ArduinoFirmata] STRING_DATA: {0}", str);

                }
                else if (parse_buf[1] == (int)Types.Messages.PIN_REPORT_RESPONSE)
                {
                    try
                    {
                        Arduino.Firmata.Types.Pins pin = (Arduino.Firmata.Types.Pins)parse_buf[2];
                        int report_mode = parse_buf[3];

                        if (report_mode != 0 && report_mode != 1)
                        {
                            //Console.WriteLine("[i][ArduinoFirmata] PIN_REPORT_RESPONSE: pin = {0}, something wrong, report mode is {1}", pin, report_mode);
                        }
                        else
                        {
                            if (!pinsReportMode.ContainsKey(pin)) pinsReportMode.Add(pin, false);
                            if (report_mode == 0) pinsReportMode[pin] = false;
                            else if (report_mode == 1) pinsReportMode[pin] = true;

                            //Console.WriteLine("[i][ArduinoFirmata] PIN_REPORT_RESPONSE: pin = {0}, report mode = {1}", pin, report_mode);
                        }
                    }
                    catch (Exception)
                    {

                    }
                }
                return;
            }
        }

        #endregion

    }

}
